Análisis Exploratorio De Datos

Authors

Mateo Yáñez Tanaka

Jorge Sánchez Ponce

Fernando Ramos Valdez

Fernando Pavía González

En este notebook se seguiran todos los pasos necesarios para el análisis exploratorio de los datos.

Limpieza de Datos

Comenzaremos por cargar y limpiar la base de datos, asegurandonos que todo se encuentre en la forma más fácil de usar. Es decir todas las variables se deben de encontrar tipificadas propiamente las columnas nombradas de manera que sean fáciles de acceder y los datos en condiciones en las cuáles se pueda realizar inferencia.

import pandas as pd
import numpy as np
from scipy import stats as stats
import matplotlib.pyplot as plt
import seaborn as sns

data = pd.read_csv('../Data/data_globant.csv')
data.head()
Date Email Name Position Seniority Location Studio Client Client Tag Project Project Tag Team Name Engagement Email Leader Year Month Day
0 02Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 3.04 laura.leon@tec.globant.com 2023 1 2
1 03Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.99 laura.leon@tec.globant.com 2023 1 3
2 04Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.97 laura.leon@tec.globant.com 2023 1 4
3 05Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.75 laura.leon@tec.globant.com 2023 1 5
4 06Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 3.15 laura.leon@tec.globant.com 2023 1 6

Los datos contienen muchas variables categóricas junto con otras tantas numéricas. En específico las variables que nos dan mayor contexto sobre los datos son aquellas como la fecha en la que se registró(Date), la antigüedad de la persona (Seniority), la posición que ejerce (Position), su equipo (TeamName) y finalmente el engagement que es el valor con el que más importa que trabajemos (Engagement). Ahora sería relevante ver que todas las variables estén nombradas de maneras en las que sean de fácil acceso (sin espacios y en únicamente minúsculas)

data.columns = [col.lower() for col in data.columns]
data.columns = [col.replace(' ','_') for col in data.columns]

data.head()
date email name position seniority location studio client client_tag project project_tag team_name engagement email_leader year month day
0 02Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 3.04 laura.leon@tec.globant.com 2023 1 2
1 03Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.99 laura.leon@tec.globant.com 2023 1 3
2 04Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.97 laura.leon@tec.globant.com 2023 1 4
3 05Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 2.75 laura.leon@tec.globant.com 2023 1 5
4 06Jan23 natalia.ramirez@tec.globant.com Natalia Ramírez Software Developer Jr CO/ANT/MED Engineering GreenWave Innovations GWI001 Atlas Initiative ATLINT Breaking Badger 3.15 laura.leon@tec.globant.com 2023 1 6

Con los nombres de las columnas estandarizados verificaremos que todas las columnas estén tipificadas propiamente, es decir que los datos numéricos tengan un tipo de dato numérico y los que no deberían de tenerlo no lo tengan

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 11366 entries, 0 to 11365
Data columns (total 17 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   date          11366 non-null  object 
 1   email         11366 non-null  object 
 2   name          11366 non-null  object 
 3   position      11366 non-null  object 
 4   seniority     11366 non-null  object 
 5   location      11366 non-null  object 
 6   studio        11366 non-null  object 
 7   client        11366 non-null  object 
 8   client_tag    11366 non-null  object 
 9   project       11366 non-null  object 
 10  project_tag   11366 non-null  object 
 11  team_name     11366 non-null  object 
 12  engagement    11366 non-null  float64
 13  email_leader  10226 non-null  object 
 14  year          11366 non-null  int64  
 15  month         11366 non-null  int64  
 16  day           11366 non-null  int64  
dtypes: float64(1), int64(3), object(13)
memory usage: 1.5+ MB

Todos los datos están tipificados propiamente, entonces entramos a la fase final en la que nos desharemos de columnas innecesarias y redundantes. Para esto es necesario que recordemos el propósito de este proyecto: Intentar predecir cuándo un empleado está por bajar su engagement. Para esto conservaremos solo ciertas cosas - date es la fecha y es altamente relevante para saber cuándo se observó ese engagement - name es el nombre del empleado y servirá para poder identificarlo - position es el puesto que ejercen dentro de la empresa y nos servirá para pruebas estadísticas - seniority es la antigüedad en la empresa y servirá para realizar pruebas estadísticas - location se refiere a la ubicación de la rama en la que trabaja el empleado. Se usará para pruebas estadísticas. - studio es el área en la que trabaja. Se usará para pruebas estadísticas - client se refiere al cliente con el que está trabajando. Se usará para pruebas estadísticas - project es el proyecto en el que trabaja. Se usará para pruebas estadísticas - team_name es el nombre del equipo en el que trabaja. Se usará para pruebas estadísticas - engagement es el engagement que intentaremos predecir, será nuestra variable de respuesta

El resto de las variables se removerán, junto con los registros duplicados.

data.drop(columns=['email','client_tag','project_tag','email_leader','year','month','day'], inplace = True)
data.drop_duplicates(inplace=True)
data['date'] = pd.to_datetime(data['date'], format='%d%b%y')

data.to_csv('../Data/data_globant_clean.csv')

Con esto terminamos la limpieza de los datos y podemos proceder con el análisis exploratorio.

Análisis Exploratorio

Como parte del análisis exploratorio de estos datos decidimos considerar si realizar pruebas de hipótesis sería una buena idea.

Entendiendo las distribuciones

Para decidir si este tipo de prueba sería una buena idea comenzamos viendo la distribución de los datos de la variable de engagement que será la que intentaremos predecir más adelante.

plt.title('Distribución de valores de engagement')
plt.xlabel('Puntaje de engagement')
plt.ylabel('Frecuencia')
plt.hist(data['engagement'], bins=30, color='purple',density=True);
plt.savefig('../Figures/EngagementDist.png')
plt.show()

Como se puede notar por su histograma la distribución de estos datos no es precisamente normal ya que hay demasiados valores muy bajos y muy altos (cerca de 0 y cerca de 5) además de que existe un espacio vacío en general entre 1 y 1.5 aproximadamente. Ahora veremos como se distribuye esto si separamos por equipos

n = data['team_name'].nunique()
teams = list(data['team_name'].unique())

fig, axs = plt.subplots(int(np.ceil(n/2)),2, figsize= (20,20));

row = 0
col = 0

for team in teams:
    d = data[data['team_name'] == team]
    mean = data['engagement'].mean()
    if col == 2:
        row += 1
        col = 0
    axs[row, col].hist(d['engagement'],bins=30,color= 'purple');
    axs[row,col].axvline(mean, color='red', linestyle='--', linewidth=2)
    axs[row, col].set_title(f'Distribución de engagement de {team}')
    axs[row, col].set_xlabel('Engagement')
    axs[row, col].set_ylabel('Frecuencia')
    col += 1

fig.suptitle('Distribución de engagement por equipo')
plt.savefig('../Figures/EngagementEquipo.png')
plt.tight_layout()
plt.show()

Con estas gráficas ya creadas se puede notar que el engagement o muy bajo o muy alto no son características de un equipo en especial a lo largo de todo el tiempo en el que se recabaron estos datos. A pesar de esto si es relevante notar que las distribuciones del engagement si cambian dependiendo del equipo del que se trarta.

Ahora sería relevante encontrar si el engagement general baja en alguna fecha, para esto seccionaremos en los 12 meses del año y veremos si alguno presenta una distribución significativamente diferente.

months = [1,2,3,4,5,6]
month_names = ['Enero','Febrero','Marzo','Abril','Mayo','Junio']
fig, axs =  plt.subplots(3,2,figsize=(15,15))
row = 0
col = 0

for i, month in enumerate(months):
    d = data[data['date'].dt.month == month]
    mean = d['engagement'].mean()

    if col == 2:
        row += 1
        col = 0

    axs[row,col].hist(d['engagement'], bins = 30, color = 'purple');
    axs[row,col].axvline(mean, color='red', linestyle='--', linewidth=2)
    axs[row,col].set_title(f'Distribución de engagement en {month_names[i]}')
    axs[row,col].set_xlabel('Engagement')
    axs[row,col].set_ylabel('Frecuencia')
    col += 1

fig.suptitle('Distribución de engagement por mes')
plt.savefig('../Figures/EgagementMes.png')
plt.tight_layout()
plt.show()

Las distribuciones no parecen ser significativamente diferentes por mes. Aunque si existe información relevante, como que le mes con el engagement más alto en promedio es Enero donde excede 3, y que de ahí en más el promedio se ve reducido en el resto de los meses manteniendose igual o menor a 3. Esto sugiere que el engagement no se reduce significativamente.

Esto nos muestra que las pruebas de hipótesis no serían el mejor enfoque para poder entender estos datos.

Líneas de Tiempo

Cambiando el enfoque de pruebas de hipótesis, decidimos generar líneas del tiempo segmentando por equipo para ver si sus conductas cambian con el tiempo. Estas gráficas se realizaran por cada uno de los días en los que trabajo cada equipo para evitar valores nulos en la recabación y potencialmente poder agregar esrta información a la base de datos. El engagement de cada equipo se considerará como el promedio del engagement de sus miembros en cualquier fecha dada.

team_engagements = []
team_dates = []

for team in teams:
    team_data = data[data['team_name'] == team].sort_values('date')
    group = team_data.groupby('date', as_index=False)['engagement'].mean()
    
    team_engagements.append(group['engagement'].values)
    team_dates.append(group['date'].values)

fig, axs = plt.subplots(5,2,figsize=(20,20))
row = 0
col = 0

for i in range(0,10):
    x = team_dates[i]
    y = team_engagements[i]
    if col == 2:
        row += 1
        col = 0
    axs[row,col].plot(x,y);
    axs[row,col].set_title(f'Linea del tiempo del engagement de {teams[i]}')
    axs[row,col].set_xlabel('Fecha')
    axs[row,col].set_ylabel('Engagement del equipo (promedio)')
    col +=1
fig.suptitle('Linea del tiempo de engagement por equipo')
plt.savefig('../Figures/TimelineEquipos.png')
plt.tight_layout()
plt.show()

Estas gráficas nos proporcionan un poco más de información sobre las distinciones entre los engagements de los equipos, ya que, como se puede notar en la gráfica de Finding Nemo’s Friends, The Great Catsby y Fight Club Penguin a pesar de que los promedios generales y distribuciones del engagement en los meses hayan sido muy similares se puede ver claramente que el performance promedio de estos equipo disminuye a lo largo del tiempo.

Ahora se realizará un análisis similar para una muestra de 10 personas de manera aleatoria para ver si se puede observar alguna tendencia en sus engagements personales.

np.random.seed(42)
people = np.random.choice(data['name'].unique(),size = 10, replace= False)

people_engagements = []
people_dates = []

for person in people:
    person_data = data[data['name']==person].sort_values('date')
    person_data = person_data.groupby('date', as_index = False)['engagement'].mean()

    people_engagements.append(person_data['engagement'].values)
    people_dates.append(person_data['date'].values)

fig, axs = plt.subplots(5,2,figsize = (20,20))
row = 0
col = 0

for i in range(0,10):
    x = people_dates[i]
    y = people_engagements[i]
    if col == 2:
        row += 1
        col = 0
    axs[row,col].plot(x,y);
    axs[row,col].set_title(f'Linea del tiempo del engagement de {people[i]}')
    axs[row,col].set_xlabel('Fecha')
    axs[row,col].set_ylabel('Engagement')
    col += 1 
fig.suptitle('Linea del tiempo de engagement personal')
plt.savefig('../Figures/TimelinePersonas.png')
plt.tight_layout()
plt.show()

Aquí se pueden notar algunos patrones bastante más obvios que muestran que el engagement en efecto decrece con el tiempo para ciertas personas. Esto se puede ver especialmente en los casos de Sofia Delgado y Ana Gómez que pertenecen a los eqipos de Finding Nemo’s Friends y Breaking Badger respectivamente. En ambos casos sus engagements decrecen de manera bastante estable y luego se estancan en un valor alrededor de 3 el cual es el valor promedio del engagement. Adicionalmente se puede notar que varios parecen no seguir mucho un patrón y simplemente oscilar en los valores altos con alguno que otro cero marcando una inasistencia para ese miembro del equipo, un ejemplo de este tipo de conducta se presenta en el caso de Natalia Ramirez y Ana Salas. Finalmente se puede encontrar otro tipo de patrón diferente en el que los valores oscilan en los números altos, luego bajan por un periodo de tiempo y finalmente suben otra vez, esto se puede ver en los casos de Alberto Bravo y Paula Cordero. Estos patrones sugieren que no existe un patrón general para el engagement en todos los empleados y que un modelo sería mejor si intenta predecir los engagements personales.